package com.example.gpay

import android.app.Activity
import android.app.Application
import android.content.Context
import android.util.Log
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.AcknowledgePurchaseResponseListener
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClient.BillingResponseCode
import com.android.billingclient.api.BillingClient.ProductType
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.ConsumeResponseListener
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.Purchase.PurchaseState
import com.android.billingclient.api.PurchasesResponseListener
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.QueryProductDetailsParams
import com.android.billingclient.api.QueryPurchasesParams
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject

object GPay {
    private lateinit var payHandle: GPayHandle

    private val scope = CoroutineScope(Dispatchers.IO)
    private lateinit var billingClient: BillingClient

    private val purchaseUpdatedListenerMap = hashMapOf<String, PurchasesUpdatedListener>()
    private val productDetailsCacheMap = hashMapOf<String, ProductDetails>()
    private var reconnectJob: Job? = null

    /**
     * 购买监听
     */
    private val purchasesUpdatedListener = object : PurchasesUpdatedListener {
        override fun onPurchasesUpdated(p0: BillingResult, p1: MutableList<Purchase>?) {
            p1?.forEach { purchase ->
                val products = purchase.products
                products.firstOrNull()?.let { productId ->
                    purchaseUpdatedListenerMap[productId]?.onPurchasesUpdated(p0, listOf(purchase))
                }
            } ?: let {
                purchaseUpdatedListenerMap.forEach {
                    it.value.onPurchasesUpdated(p0, p1)
                }
            }

        }
    }

    private val billingClientStateListener = object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            // The BillingClient is ready. You can query purchases here.
            val connectionState = billingClient.connectionState
            if (connectionState == BillingClient.ConnectionState.CONNECTED) {
                restore()
            } else {
                reconnect()
            }
        }

        override fun onBillingServiceDisconnected() {
            // Try to restart the connection on the next request to
            // Google Play by calling the startConnection() method.
            reconnect()
        }
    }

    /**
     * 初始化，无需主动调用
     */
    fun init(context: Application, payHandle: GPayHandle) {
        if (::billingClient.isInitialized) {
            return
        }
        this.payHandle = payHandle
        billingClient = BillingClient.newBuilder(context)
            .setListener(purchasesUpdatedListener)
            .enablePendingPurchases()
            .build()
        billingClient.startConnection(billingClientStateListener)
    }

    private fun reconnect() {
        reconnectJob?.cancel()
        reconnectJob = scope.launch {
            delay(3000)
            billingClient.startConnection(billingClientStateListener)
        }
    }

    /**
     * 补单（可主动多次调用）
     * @return 是否调用成功，Google结算服务未成功连接时不会查询则返回false
     */
    fun restore(): Boolean {
        if (billingClient.connectionState == BillingClient.ConnectionState.CONNECTED) {
            //补单
            //消耗商品
            billingClient.queryPurchasesAsync(
                QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build()
            ) { p0, p1 ->
                p1.forEach {
                    scope.launch {
                        //验证
                        payHandle.runCatching {
                            logRestoreVerifyOrder(JSONObject().apply {
                                put("msg", "log restore verify order")
                                put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                put("originalJson", it.originalJson)
                            }.toString())
                        }
                        val verifyResult = onVerifyPurchase(it)
                        payHandle.runCatching {
                            logRestoreVerifyOrderResp(JSONObject().apply {
                                put("msg", "log restore verify order resp")
                                put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                put("originalJson", it.originalJson)
                                put("verifyResult", "$verifyResult")
                            }.toString())
                        }
                        if (verifyResult.responseCode == BillingResponseCode.OK) {
                            payHandle.runCatching {
                                logRestoreConsumeOrder(JSONObject().apply {
                                    put("msg", "log restore consume order")
                                    put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                    put("originalJson", it.originalJson)
                                    put("verifyResult", verifyResult)
                                }.toString())
                            }
                            val consumeResult = onConsume(it)
                            payHandle.runCatching {
                                logRestoreConsumeOrder(JSONObject().apply {
                                    put("msg", "log restore consume order resp")
                                    put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                    put("originalJson", it.originalJson)
                                    put("consumeResult", "$consumeResult")
                                    put("verifyResult", verifyResult)
                                }.toString())
                            }
                        }
                    }

                }
            }

            //订阅商品
            billingClient.queryPurchasesAsync(
                QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build()
            ) { p0, p1 ->

                p1.forEach {

                    //已经确认的订阅则忽略
                    if (it.isAcknowledged) {
                        return@forEach
                    }

                    scope.launch {
                        //验证
                        payHandle.runCatching {
                            logRestoreVerifyOrder(JSONObject().apply {
                                put("msg", "log restore verify order")
                                put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                put("originalJson", it.originalJson)
                            }.toString())
                        }
                        val verifyResult = onVerifyPurchase(it)

                        payHandle.runCatching {
                            logRestoreVerifyOrder(JSONObject().apply {
                                put("msg", "log restore verify order resp")
                                put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                put("originalJson", it.originalJson)
                                put("verifyResult", verifyResult)
                            }.toString())
                        }
                        if (verifyResult.responseCode == BillingResponseCode.OK) {

                            payHandle.runCatching {
                                logRestoreAcknowledgedOrder(JSONObject().apply {
                                    put("msg", "log restore acknowledged order")
                                    put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                    put("originalJson", it.originalJson)
                                    put("verifyResult", verifyResult)
                                }.toString())
                            }
                            val acknowledgeResult = onAcknowledgePurchase(it)

                            payHandle.runCatching {
                                logRestoreAcknowledgedOrder(JSONObject().apply {
                                    put("msg", "log restore acknowledged order resp")
                                    put("orderNo", it.accountIdentifiers?.obfuscatedAccountId ?: "")
                                    put("originalJson", it.originalJson)
                                    put("verifyResult", verifyResult)
                                    put("acknowledgeResult", acknowledgeResult)
                                }.toString())
                            }
                        }
                    }

                }
            }
            return true
        }
        return false
    }

    /**
     * 购买
     * @param orderNo 服务器订单id
     * @param productId 商品id
     * @param productType 购买类型 普通商品: ProductType.INAPP，订阅: ProductType.SUBS
     * @return 判断GPayResponse.response
     * 购买成功：BillingResponseCode.OK
     * 取消购买：BillingResponseCode.USER_CANCELED
     * 其他均为失败，其中服务不可用：BillingResponseCode.BILLING_UNAVAILABLE可提示用户检查google账号状态
     * 其他可查看BillingClient.BillingResponseCode中的定义
     *
     * 注：在IO协程中执行
     */
    suspend fun purchase(activity: Activity, orderNo: String, productId: String, @ProductType productType: String): GPayResponse {

        if (billingClient.connectionState != BillingClient.ConnectionState.CONNECTED) {
            return GPayResponse(
                response = BillingResponseCode.BILLING_UNAVAILABLE,
                message = "Google Pay is not available, please check account status"
            )
        }

        val result = runCatching<GPayResponse> {
            payHandle.runCatching {
                logReviewOrder(JSONObject().apply {
                    put("msg", "log review order")
                    put("orderNo", orderNo)
                    put("productId", productId)
                    put("productType", productType)
                }.toString())
            }
            val productDetailsResult = onQueryProductDetails(listOf(productId), productType)
            payHandle.runCatching {
                logReviewOrderResp(JSONObject().apply {
                    put("msg", "log review order resp")
                    put("orderNo", orderNo)
                    put("productId", productId)
                    put("productType", productType)
                }.toString())
            }
            if (productDetailsResult.result.responseCode != BillingResponseCode.OK || productDetailsResult.productDetailsList.isNullOrEmpty()) {
                return@runCatching GPayResponse(BillingResponseCode.ERROR, productDetailsResult.result.debugMessage)
            }

            val productDetailsParamsList = listOf(
                BillingFlowParams.ProductDetailsParams.newBuilder().apply {
                    val productDetails = productDetailsResult.productDetailsList.first()
                    setProductDetails(productDetails)
                    //订阅商品才需要setOfferToken
                    if (productType == ProductType.SUBS) {
                        val subscriptionOfferDetails = productDetails.subscriptionOfferDetails
                        subscriptionOfferDetails?.getOrNull(0)?.let {
                            setOfferToken(it.offerToken)
                        }
                    }
                }.build()
            )

            val billingFlowParams = BillingFlowParams.newBuilder()
                .setProductDetailsParamsList(productDetailsParamsList)
                .setObfuscatedAccountId(orderNo)
                .build()

            //拉起支付
            val launchBillingFlowResult = withContext(Dispatchers.Main) {
                payHandle.runCatching {
                    logLaunchPay(JSONObject().apply {
                        put("msg", "log launch pay")
                        put("orderNo", orderNo)
                        put("productId", productId)
                        put("productType", productType)
                    }.toString())
                }
                val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)


                return@withContext billingResult
            }
            if (launchBillingFlowResult.responseCode != BillingClient.BillingResponseCode.OK) {
                payHandle.runCatching {
                    logLaunchPayResp(JSONObject().apply {
                        put("msg", "log launch pay resp")
                        put("orderNo", orderNo)
                        put("productId", productId)
                        put("productType", productType)
                        put("billingResult", launchBillingFlowResult.toString())
                    }.toString())
                }
                return@runCatching GPayResponse(launchBillingFlowResult.responseCode, message = launchBillingFlowResult.debugMessage)
            }


            //设置支付结果监听
            val purchasesCompletableDeferred = CompletableDeferred<PurchasesUpdateResult>()
            purchaseUpdatedListenerMap[productId] = object : PurchasesUpdatedListener {
                override fun onPurchasesUpdated(p0: BillingResult, p1: MutableList<Purchase>?) {
                    purchasesCompletableDeferred.complete(PurchasesUpdateResult(result = p0, purchases = p1))
                }
            }

            //等待支付结果
            val purchasesUpdateResult = purchasesCompletableDeferred.await()
            payHandle.runCatching {
                logLaunchPayResp(JSONObject().apply {
                    put("msg", "log launch pay resp")
                    put("orderNo", orderNo)
                    put("productId", productId)
                    put("productType", productType)
                    put("purchasesUpdateResult", purchasesUpdateResult.result.toString())
                    put("purchasesUpdatePurchase", purchasesUpdateResult.purchases?.firstOrNull()?.originalJson ?: "")
                }.toString())
            }


            when (purchasesUpdateResult.result.responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    purchasesUpdateResult.purchases?.forEach { purchase ->
                        purchase.products.find {
                            //找到对应productId充值商品
                            return@find productId == it
                        }?.let {
                            productDetailsCacheMap[productId]?.let {

                                //校验
                                payHandle.runCatching {
                                    logVerifyOrder(JSONObject().apply {
                                        put("msg", "log verify order")
                                        put("orderNo", orderNo)
                                        put("productId", productId)
                                        put("productType", productType)
                                        put("purchasesUpdatePurchase", purchase.originalJson)
                                    }.toString())
                                }
                                val verifyResult = onVerifyPurchase(purchase)
                                payHandle.runCatching {
                                    logVerifyOrderResp(JSONObject().apply {
                                        put("msg", "log verify order resp")
                                        put("orderNo", orderNo)
                                        put("productId", productId)
                                        put("productType", productType)
                                        put("purchasesUpdatePurchase", purchase.originalJson)
                                        put("verifyResult", verifyResult.toString())
                                    }.toString())
                                }

                                val result = if (verifyResult.responseCode == BillingResponseCode.OK) {
                                    when (it.productType) {
                                        //消耗型购买
                                        ProductType.INAPP -> {
                                            //消耗
                                            payHandle.runCatching {
                                                logConsumeOrder(JSONObject().apply {
                                                    put("msg", "log consume order")
                                                    put("orderNo", orderNo)
                                                    put("productId", productId)
                                                    put("productType", productType)
                                                    put("purchasesUpdatePurchase", purchase.originalJson)
                                                    put("verifyResult", verifyResult.toString())
                                                }.toString())
                                            }
                                            val consumeResult = onConsume(purchase)
                                            payHandle.runCatching {
                                                logConsumeOrderResp(JSONObject().apply {
                                                    put("msg", "log consume order resp")
                                                    put("orderNo", orderNo)
                                                    put("productId", productId)
                                                    put("productType", productType)
                                                    put("purchasesUpdatePurchase", purchase.originalJson)
                                                    put("consumeResult", consumeResult.toString())
                                                    put("verifyResult", verifyResult.toString())

                                                }.toString())
                                            }
                                            consumeResult
                                        }
                                        //订阅型购买
                                        ProductType.SUBS -> {
                                            //确认订阅
                                            payHandle.runCatching {
                                                logAcknowledgedOrder(JSONObject().apply {
                                                    put("msg", "log acknowledged order")
                                                    put("orderNo", orderNo)
                                                    put("productId", productId)
                                                    put("productType", productType)
                                                    put("purchasesUpdatePurchase", purchase.originalJson)
                                                    put("verifyResult", verifyResult.toString())

                                                }.toString())
                                            }
                                            val acknowledgeResult = onAcknowledgePurchase(purchase)
                                            payHandle.runCatching {
                                                logAcknowledgedOrderResp(JSONObject().apply {
                                                    put("msg", "log acknowledged order resp")
                                                    put("orderNo", orderNo)
                                                    put("productId", productId)
                                                    put("productType", productType)
                                                    put("purchasesUpdatePurchase", purchase.originalJson)
                                                    put("consumeResult", acknowledgeResult.toString())
                                                    put("verifyResult", verifyResult.toString())

                                                }.toString())
                                            }
                                            acknowledgeResult
                                        }
                                        //未知类型
                                        else -> {
                                            BillingResult.newBuilder()
                                                .setDebugMessage("Unknown product type")
                                                .setResponseCode(BillingResponseCode.ERROR)
                                                .build()
                                        }
                                    }
                                } else {
                                    verifyResult
                                }
                                return@runCatching if (result.responseCode == BillingClient.BillingResponseCode.OK) {
                                    //消耗/确认成功
                                    GPayResponse(result.responseCode, "Success")
                                } else {
                                    //消耗/确认失败
                                    GPayResponse(result.responseCode, result.debugMessage)
                                }
                            }


                        }
                    }
                    return@runCatching GPayResponse(BillingResponseCode.ERROR, "Purchase data is empty")
                }

                else -> {
                    return@runCatching GPayResponse(purchasesUpdateResult.result.responseCode, purchasesUpdateResult.result.debugMessage)
                }
            }

        }

        result.exceptionOrNull()?.let {
            purchaseUpdatedListenerMap.remove(productId)
            return GPayResponse(response = BillingResponseCode.ERROR, message = it.message ?: "purchase error")
        }

        val response = result.getOrDefault(GPayResponse(response = BillingResponseCode.ERROR, message = "purchase error"))
        purchaseUpdatedListenerMap.remove(productId)
        return response
    }

    /**
     * 验证购买
     */
    private suspend fun onVerifyPurchase(purchase: Purchase): BillingResult {
        val result = payHandle.runCatching {
            this.onVerifyPurchase(purchase)
        }
        result.exceptionOrNull()?.let {
            return BillingResult.newBuilder()
                .setResponseCode(BillingResponseCode.ERROR)
                .setDebugMessage(it.message ?: "Verify purchase error")
                .build()
        }

        val verify = result.getOrDefault(false)
        return if (verify) {
            BillingResult.newBuilder()
                .setResponseCode(BillingResponseCode.OK)
                .setDebugMessage("Verify purchase success")
                .build()
        } else {
            BillingResult.newBuilder()
                .setResponseCode(BillingResponseCode.ERROR)
                .setDebugMessage("Verify purchase failed")
                .build()
        }
    }

    /**
     * 查询商品详情
     */
    private suspend fun onQueryProductDetails(productIds: List<String>, productType: String): ProductDetailsResult {
        val result = runCatching<ProductDetailsResult> {
            val queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(
                arrayListOf<QueryProductDetailsParams.Product?>().apply {
                    productIds.forEach {
                        add(
                            QueryProductDetailsParams.Product.newBuilder()
                                .setProductId(it)
                                .setProductType(productType)
                                .build()
                        )
                    }
                }
            ).build()
            val queryProductDetailsCompletableDeferred = CompletableDeferred<ProductDetailsResult>()

            //查询商品
            billingClient.queryProductDetailsAsync(queryProductDetailsParams) { billingResult, productDetailsList ->
                queryProductDetailsCompletableDeferred.complete(
                    ProductDetailsResult(
                        result = billingResult,
                        productDetailsList = productDetailsList
                    )
                )
            }

            //等待商品查询结果
            val productDetailsResult = queryProductDetailsCompletableDeferred.await()
            productDetailsResult.productDetailsList?.forEach {
                //缓存商品详情
                productDetailsCacheMap[it.productId] = it
            }
            if (productDetailsResult.result.responseCode == BillingResponseCode.OK && productDetailsResult.productDetailsList.isNullOrEmpty()) {
                return@runCatching ProductDetailsResult(
                    result = BillingResult.newBuilder()
                        .setResponseCode(BillingResponseCode.ERROR)
                        .setDebugMessage("product details list empty")
                        .build(),
                    productDetailsList = listOf()
                )
            }
            return@runCatching productDetailsResult
        }
        result.exceptionOrNull()?.let {
            return ProductDetailsResult(
                result = BillingResult.newBuilder().setResponseCode(BillingResponseCode.ERROR).setDebugMessage(it.message ?: "").build(),
                productDetailsList = listOf()
            )
        }
        return result.getOrDefault(
            ProductDetailsResult(
                result = BillingResult.newBuilder()
                    .setResponseCode(BillingResponseCode.ERROR)
                    .setDebugMessage("query product details failed")
                    .build(),
                productDetailsList = listOf()
            )
        )
    }

    /**
     * 购买消耗
     */
    private suspend fun onConsume(purchase: Purchase): BillingResult {

        if (purchase.purchaseState != PurchaseState.PURCHASED) {
            return BillingResult.newBuilder()
                .setDebugMessage("The purchase status is not the purchased status")
                .setResponseCode(BillingResponseCode.ERROR)
                .build()
        }

        runCatching {

            val consumeCompletableDeferred = CompletableDeferred<BillingResult>()

            val consumeParams =
                ConsumeParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
                    .build()
            billingClient.consumeAsync(consumeParams, object : ConsumeResponseListener {
                override fun onConsumeResponse(p0: BillingResult, p1: String) {
                    consumeCompletableDeferred.complete(p0)
                }
            })
            val result = consumeCompletableDeferred.await()
            return@runCatching result
        }.onSuccess {
            if (it.responseCode == BillingResponseCode.OK) {
                scope.launch(Dispatchers.Main) {
                    payHandle.runCatching { onConsumeSuccess(purchase) }
                }
            }
            return it
        }.onFailure {
            return BillingResult.newBuilder().setResponseCode(BillingResponseCode.ERROR).setDebugMessage(it.message ?: "").build()
        }
        return BillingResult.newBuilder().setResponseCode(BillingResponseCode.ERROR).setDebugMessage("Consume error").build()
    }

    /**
     * 订阅确认
     */
    private suspend fun onAcknowledgePurchase(purchase: Purchase): BillingResult {
        if (purchase.isAcknowledged) {
            return BillingResult.newBuilder()
                .setResponseCode(BillingResponseCode.ERROR)
                .setDebugMessage("Purchase has been acknowledged")
                .build()
        }

        runCatching {

            val completableDeferred = CompletableDeferred<BillingResult>()

            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                .setPurchaseToken(purchase.purchaseToken)
            billingClient.acknowledgePurchase(acknowledgePurchaseParams.build(), object : AcknowledgePurchaseResponseListener {
                override fun onAcknowledgePurchaseResponse(p0: BillingResult) {
                    completableDeferred.complete(p0)
                }
            })
            val result = completableDeferred.await()
            return@runCatching result
        }.onSuccess {
            if (it.responseCode == BillingResponseCode.OK) {
                scope.launch(Dispatchers.Main) {
                    payHandle.runCatching { onAcknowledgePurchaseSuccess(purchase) }
                }
            }
            return it
        }.onFailure {
            return BillingResult.newBuilder().setResponseCode(BillingResponseCode.ERROR).setDebugMessage(it.message ?: "").build()
        }
        return BillingResult.newBuilder().setResponseCode(BillingResponseCode.ERROR).setDebugMessage("Acknowledge error").build()


    }

}

data class GPayResponse(@BillingResponseCode val response: Int, val message: String)

data class PurchasesUpdateResult(val result: BillingResult, val purchases: List<Purchase>?)
data class ProductDetailsResult(val result: BillingResult, val productDetailsList: List<ProductDetails>?)
